﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Text;
using Pahan.McuFusion.Common;
using Pahan.McuFusion.Common.Utils;

namespace Pahan.McuFusion.Atmel.Avr
{
    internal class AvrWithPagedMemoryProgrammer// : AtmelProgrammer
    {
        /*
        public AvrWithPagedMemoryProgrammer(AvrMcuInfo mcu, AtmelProgrammerInfo info, IAtmelSpiAdapter adapter) : base(info, adapter)
        {
            this.mcuInfo = mcu;
        }

        #region Overrides of AtmelProgrammer

        /// <summary>
        /// Erases the chip.
        /// </summary>
        protected override void DoEraseChip(AsyncOperationArgs args)
        {
            var instruction = GetChipEraseInstruction();
            Adapter.PerformIO(instruction);
            WaitHelper.Wait(mcuInfo.EraseDelay * 10);
        }

        /// <summary>
        /// Reads the program memory.
        /// </summary>
        protected override void DoReadProgramMemory(AsyncOperationArgs args, Stream output)
        {
            IList<byte> instruction;
            int size = mcuInfo.Memory[0].MemorySize / 2;
            for (uint i = 0; i < size; i++)
            {
                if (args.CancellationPending)
                {
                    args.Cancel = true;
                    break;
                }
                byte value;
                IList<byte> tmp;
                instruction = GetReadProgramMemoryInstruction(i, false);
                tmp = Adapter.PerformIO(instruction);
                value = tmp[3];
                output.WriteByte(value);
                instruction = GetReadProgramMemoryInstruction(i, true);
                tmp = Adapter.PerformIO(instruction);
                value = tmp[3];
                output.WriteByte(value);
                args.Progress = (int)((i + 1) * 100 / size );
            }
            output.Flush();
        }

        /// <summary>
        /// Reads the EEPROM.
        /// </summary>
        protected override void DoReadEeprom(AsyncOperationArgs args, Stream output)
        {
            IList<byte> instruction;
            byte value;
            int size = mcuInfo.Memory[1].MemorySize;
            for (uint i = 0; i < size; i++)
            {
                if (args.CancellationPending)
                {
                    args.Cancel = true;
                    break;
                }
                instruction = GetReadEepromInstruction(i);
                value = Adapter.PerformIO(instruction)[3];
                output.WriteByte(value);
            }
            output.Flush();
        }

        /// <summary>
        /// Writes the program memory.
        /// </summary>
        protected override void DoWriteProgramMemory(AsyncOperationArgs args, Stream input)
        {
            IList<byte> rom = ReadProgramMemoryFile(input);
            
            for (int i = 0; i < mcuInfo.Memory[0].PageCount; i++)
            {
                if (args.CancellationPending)
                {
                    args.Cancel = true;
                    break;
                }
                int wordAddress = i * mcuInfo.Memory[0].PageCount / 2;
                var page = GetPage(rom, wordAddress);
                LoadPage(page, wordAddress);
                var writePageInstruction = GetWriteProgramMemoryPageInstruction(wordAddress);
                Adapter.PerformIO(writePageInstruction);
                WaitHelper.Wait(mcuInfo.FlashDelay * 2);
            }
        }

        /// <summary>
        /// Writes the EEPROM.
        /// </summary>
        protected override void DoWriteEeprom(AsyncOperationArgs args, Stream input)
        {
            IList<byte> instruction;
            int value;
            uint address = 0;
            while ((value = input.ReadByte()) > 0)
            {
                if (args.CancellationPending)
                {
                    args.Cancel = true;
                    break;
                }
                instruction = GetWriteEepromInstruction(address, (byte)value);
                Adapter.PerformIO(instruction);
                WaitHelper.Wait(mcuInfo.EepromDelay);
                address++;
            }
        }

        #endregion

        #region Overrides
        
        protected override void DoOpen()
        {
            base.DoOpen();
            var instruction = GetProgrammingEnableInstruction();
            var result = Adapter.PerformIO(instruction);
            if (result[2] != instruction[1])
            {
                throw new InvalidOperationException("Communication is out of synchronization.");
            }
        }

        protected new IAtmelSpiAdapter Adapter
        {
            get
            {
                return (IAtmelSpiAdapter)base.Adapter;
            }
        }

        #endregion

        #region Serial Programming Instructions

        private IList<byte> GetProgrammingEnableInstruction()
        {
            return new byte[] { 0xAC, 0x53, 0x00, 0x00 };
        }

        private IList<byte> GetChipEraseInstruction()
        {
            return new byte[] { 0xAC, 0x80, 0x00, 0x00 };
        }

        private IList<byte> GetReadProgramMemoryInstruction(uint address, bool highByte)
        {
            byte byte1 = (byte)(highByte ? 0x28 : 0x20);
            byte byte2 = (byte)(address >> 8);
            byte byte3 = (byte)address;
            byte byte4 = 0x00;
            return new[] {byte1, byte2, byte3, byte4};
        }

        private IList<byte> GetReadEepromInstruction(uint address)
        {
            byte byte1 = 0xA0;
            byte byte2 = (byte)(address >> 8);
            byte byte3 = (byte)address;
            byte byte4 = 0x00;
            return new[] { byte1, byte2, byte3, byte4 };
        }

        private IList<byte> GetWriteProgramMemoryPageInstruction(int address)
        {
            byte byte1 = 0x4C;
            byte byte2 = (byte)(address >> 8);
            byte byte3 = (byte)(address);
            byte byte4 = 0x00;
           
            return new[] { byte1, byte2, byte3, byte4 };
        }

        private IList<byte> GetLoadProgramMemoryInstruction(int wordAddress, bool highByte, byte value)
        {
            Assert.GreaterOrEqual(wordAddress, "wordAddress", 0);
            byte byte1 = (byte)(highByte ? 0x48 : 0x40);
            byte byte2 = (byte)(wordAddress >> 8);
            byte byte3 = (byte)wordAddress;
            byte byte4 = value;
            return new[] { byte1, byte2, byte3, byte4 };
        }

        private IList<byte> GetWriteEepromInstruction(uint address, byte value)
        {
            byte byte1 = 0xC0;
            byte byte2 = (byte)(address >> 8);
            byte byte3 = (byte)address;
            byte byte4 = value;
            return new[] { byte1, byte2, byte3, byte4 };
        }

        #endregion

        #region Private Members

        private IList<byte> ReadProgramMemoryFile(Stream stream)
        {
            Assert.LessOrEqual(stream.Length, "stream.Length", mcuInfo.Memory[0].MemorySize);
            byte[] bytes = new byte[mcuInfo.Memory[0].MemorySize];
            int count = stream.Read(bytes, 0, (int)stream.Length);
            for (int i = count; i < bytes.Length; i++)
            {
                bytes[i] = 0xFF;
            }
            return bytes;
        }

        private IList<byte> GetPage(IList<byte> rom, int wordAddress)
        {
            Assert.GreaterOrEqual(wordAddress, "wordAddress", 0);
            byte[] bytes = new byte[mcuInfo.Memory[0].PageSize];
            for (int i = 0, index = wordAddress * 2; i < bytes.Length; i++, index++)
            {
                bytes[i] += rom[index];
            }
            return bytes;
        }

        private void LoadPage(IList<byte> page, int wordAddress)
        {
            Assert.GreaterOrEqual(wordAddress, "wordAddress", 0);
            IList<byte> instruction;
            int size = mcuInfo.Memory[0].PageSize / 2;
            for (int i = 0; i < size; i++, wordAddress++)
            {
                instruction = GetLoadProgramMemoryInstruction(wordAddress, false, page[2 * i]);
                Adapter.PerformIO(instruction);
                instruction = GetLoadProgramMemoryInstruction(wordAddress, true, page[2 * i + 1]);
                Adapter.PerformIO(instruction);
            }
        }

        private AvrMcuInfo mcuInfo;

        #endregion
         */
    }
}
